﻿using System.Collections.Generic;
using System.Linq;
using GCTrackableRace.Interfaces;
using GcTrackableRace.ScoreCalculator.Helpers;
using Microsoft.Practices.Unity;
using GcTrackableRace.Persistence;
using GcTrackableRace.ScoreCalculator;
using log4net.Appender;
using log4net;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using System;
using GcTrackableRace.Persistence.ScoreFiles;
using GcTrackableRace.RemoteServices;
using System.Reflection;
using System.Diagnostics;

namespace GcTrackableRace.Console
{
	class Program
	{
		static readonly IUnityContainer _Unity = new UnityContainer();

		static void Main(string[] args)
		{
			System.Console.WriteLine("GcTrackable Race");
			System.Console.WriteLine("----------------");
			System.Console.WriteLine("Version : {0}", Version);
			System.Console.WriteLine("For assistence / suggestions / bugs please email gcTrackableRace@damhuisclan.co.za");
			System.Console.WriteLine("Also email me if new rules must be added.");
			System.Console.WriteLine("This will become an open source project on GitHub once deeemed stable enough.");
			System.Console.WriteLine("For some very basic help please see here: http://www.damhuisclan.co.za/geocaching/gcTrackableRace.");
			System.Console.WriteLine("");
			
			_Unity.RegisterType<IConfigurationSettings, ConfigurationSettings>();

			var programConfigurationSettings = GetProgramConfigurationSettings(_Unity.Resolve<IConfigurationSettings>());

			RaceLogger.SetLevel("GcTrackableRaceLogger", "Debug");

			RaceLogger.AddAppender("GcTrackableRaceLogger",
				RaceLogger.CreateFileAppender("FileAppender", programConfigurationSettings.GetOutputFileNameAndLocation(FileType.TextLogFile)));

			_Unity
				.RegisterType<ILog>(new InjectionFactory(x => LogManager.GetLogger("GcTrackableRaceLogger")))
				.RegisterType<IGcTrackableRacePersistence, GcTrackableRacePersistence>(
					new InjectionConstructor(programConfigurationSettings.GcTrackableRaceDatabaseNameAndLocation));

			CheckAndCreateObjectCacheDatabase();

			_Unity
				.RegisterType<ILog>(new InjectionFactory(x => LogManager.GetLogger("GcTrackableRaceLogger")))
				.RegisterType<IConfigurationSettings, ConfigurationSettings>()
				.RegisterType<IGsakPersistence, GsakPersistence>(new InjectionConstructor(programConfigurationSettings, typeof(ILog)))
				.RegisterType<IScoringEngine, ScoringEngine>(new InjectionConstructor(typeof(IGsakPersistence), typeof(ILog)))
				.RegisterType<IGeocache, Entities.Geocache>()
				.RegisterType<IMissingCacheLog, MissingCacheLog>(new InjectionConstructor(programConfigurationSettings))
				.RegisterType<IGeocacheInfo, GeocacheInfo>()
				.RegisterType<IRemoteService, RemoteService>()
				;


			var locationInfoLookup = new LocationInfoLookup(_Unity.Resolve<IGcTrackableRacePersistence>(), _Unity.Resolve<IRemoteService>(), _Unity.Resolve<ILog>());

			_Unity
				.RegisterInstance<ILocationInfoLookup>(locationInfoLookup);

			_Unity
				.RegisterType<IScorePersistance, ScoreCsvFilePersistence>("ScoreCsvFilePersistence")
				.RegisterType<IScorePersistance, ScoreTabFilePersistence>("ScoreTabFilePersistence")
				.RegisterType<IScorePersistance, ScoreTabFileScoreAtTheEndPersistence>("ScoreTabFileScoreAtTheEndPersistence")
				.RegisterType<IScorePersistance, ScoreJsonPersistence>("ScoreJsonPersistence")
				;


			var configuration = _Unity.Resolve<IConfigurationSettings>();

			var raceRuleFiles = configuration.GetAllRaceRuleFilesFromFolder(@".\");

			if (raceRuleFiles.Any())
			{
				var raceRules = new RaceRules(_Unity, programConfigurationSettings).Create();

				var scoreEngine = _Unity.Resolve<IScoringEngine>();

				scoreEngine.ProcessChange += OnProcessChange;
				scoreEngine.Process(raceRules);

				ExportToFile(scoreEngine.Trackables, programConfigurationSettings);
			}
		}

		static void	OnProcessChange (object sender, EventArgs e)
		{
			var info = (TrackableEventArgs) e;
			System.Console.WriteLine(String.Format("{0} - {1}", info.TrackableCode, info.Detail));
		}


		private static void CheckAndCreateObjectCacheDatabase()
		{
			var objectCacheDatabase = _Unity.Resolve<IGcTrackableRacePersistence>();

			if (!objectCacheDatabase.DoesPersistenceExist())
			{
				objectCacheDatabase.CreatePersistence();
			}
		}

		private static IProgramConfigurationSettings GetProgramConfigurationSettings(IConfigurationSettings configurationSettings)
		{
			var raceRuleFiles = configurationSettings.GetAllRaceRuleFilesFromFolder(@".\");

			if (raceRuleFiles.Any())
			{
				return configurationSettings.ReadProgramConfigurationFromFile(raceRuleFiles.First());
			}

			return null;
		}

		private static void ExportToFile(IEnumerable<ITrackable> trackables, IProgramConfigurationSettings programConfigurationSettings)
		{
			foreach (var outputFormat in programConfigurationSettings.OutputFormats)
			{
				var persistence = _Unity.Resolve<IScorePersistance>(outputFormat);

				persistence.PersistTrackableScores(trackables, programConfigurationSettings);
			}
		}

		private static string Version
		{
			get
			{
				Assembly asm = Assembly.GetExecutingAssembly();
				FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location);
				return String.Format("{0}.{1}.{2}", fvi.ProductMajorPart, fvi.ProductMinorPart, fvi.ProductBuildPart);
			}
		}
	}

	class RaceLogger
	{
		// Set the level for a named logger
		public static void SetLevel(string loggerName, string levelName)
		{
			ILog log = LogManager.GetLogger(loggerName);
			
			Logger logger = (Logger)log.Logger;

			logger.Level = logger.Hierarchy.LevelMap[levelName];
		}

		// Add an appender to a logger
		public static void AddAppender(string loggerName, IAppender appender)
		{
			ILog log = LogManager.GetLogger(loggerName);
			Logger logger = (Logger)log.Logger;

			logger.AddAppender(appender);
			logger.Repository.Configured = true;
		}

		// Create a new file appender
		public static IAppender CreateFileAppender(string name, string fileName)
		{
			FileAppender appender = new FileAppender();
			appender.Name = name;
			appender.File = fileName;
			appender.AppendToFile = true;

			PatternLayout layout = new PatternLayout();
			layout.ConversionPattern = "%d - [%t] - %c - %-5p - %m%n";
			layout.ActivateOptions();

			appender.Layout = layout;
			appender.ActivateOptions();

			return appender;
		}
	}
}